package com.xiaoye.poitest;

import com.sun.org.apache.regexp.internal.RE;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.xiaoye.poitest.sets.MyDataSet;
import com.xiaoye.poitest.sets.SupplyList;
import com.xiaoye.poitest.sets.SupplyListDataSet;
import com.xiaoye.poitest.sets.User;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.*;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Class description
 * 通过Java Apache的POI实现以word文档文件为数据模板动态生成word格式的数据报表，并支持多数据集。
 * 1、程序中通过"占位符"或者"标记符"形式进行占位或标记，此程序可区分如下占位符标记
 *    格式：${XY.数据集名.域名} ，${XY[数据集名.一级属性名.二级属性名.域名]}，${XY[数据集名.一级属性名.二级属性名.ROWINDEX]}
 *    ${XY.SUM(TABLE[表索引]:列索引)}，${XY.UPPER(有意义的占位符标记)}
 *  1.1 ${XY.数据集名.域名}：普通占位符标记，理论上开发人员可以将需要展现的数据都写在一个数据集中。
 *  1.2 ${XY[数据集名.一级属性名.二级属性名.域名]}：动态数据行占位标记，规定所有的表格数据在数据集类中用List<T>表现,列表泛型
 *      为二级属性名，域名为二级属性的域名，此占位标记维护在表格列中，例如：${XY[MyDataSet.userList.User.userName]}，程序提供
 *      一个默认的域名"ROWINDEX"，行索引号，如果需要可直接调用，不需要开发人员写在数据集中，例如：${XY[MyDataSet.userList.User.ROWINDEX]}
 *  1.3 程序提供两个函数，一个XY.SUM：对指定表格的指定列进行动态数据行的求和，一个XY.UPPER，如果是金额将阿拉伯数字转为人民币金额大写，
 *      金额大小写转换，详情见XyRmbTransLowerOrUpper类。对表格中某一列进行求和，${XY.SUM(TABLE[1]:4)},此表达式被程序翻译为：
 *      将第1个表格的第4列，进行累加，累加的范围仅局限为动态数据行，此程序仅支持动态数据行的累加。至于动态数据行的开始、结束位置由程序去记忆，实现思路
 *      就是在执行新添加一个row并对其所有的列设置完毕时，将此row放进一个list进行缓存，list作为处理Table的一个局部变量，因此不会与其他Table冲突。
 * 2、怎么设置动态数据行呢？在编辑模板的时候，在动态数据行出现的第一个位置添加一行，并维护上每一列的要展示的值即可，程序在解析的时候，会把带有标记符这一行
 *    当做模板行，以此为模板行进行克隆。克隆完毕并将值设置完毕，程序会将带有标记符的模板行删掉。
 * @author xiaoqian
 * <p>
 * <p> Modification History:</p>
 * <p> Date              Author      Description </p>
 * <p>------------------------------------------------------------------</p>
 * <p>2021/1/15 9:10 AM    韩洪千                新建</p>
 */
public class XyWordTemplateUtil {
    //匹配数据集列中有没有使用UPPER函数的占位符
    private static final String regexForUpper = "\\$\\{XY\\.UPPER\\(\\$\\{XY\\.(.*?)\\.(.*?)\\}\\)\\}";
    //${XY.SUM(TABLE[1]:1)}  求第一个表格第一列所有记录的和
    private static final String sumRegx = "\\$\\{XY\\.SUM\\(TABLE\\[([1-9]\\d*)\\]\\:([1-9]\\d*)\\)\\}";
    //验证此格式：${XY.UPPER(${XY.SUM(TABLE[1]:4)})}
    private static final String upperAndSumRegex = "\\$\\{XY\\.UPPER\\(\\$\\{XY\\.SUM\\(TABLE\\[([1-9]\\d*)\\]\\:([1-9]\\d*)\\)\\}\\)\\}";
    //表达式前缀
    private static final String prefix = "XY";
    //
    private static XyWordTemplateUtil instance = null;
    //
    private XyWordTemplateUtil(){}

    public static XyWordTemplateUtil newInstance(){
        if(instance == null){
            synchronized (XyWordTemplateUtil.class){
                if(instance == null)
                    instance = new XyWordTemplateUtil();
            }
        }
        return instance;
    }

    /**
     * 解析模板并进行数据的填充替换
     * @param template
     *         word模板对象
     * @param  dataSets
     *         模板中涉及到的数据集列表，数据集必须继承自XyDataSetForTemplate
     * @return XWPFDocument
     *         被重新编辑过的word文档对象
     */
    public XWPFDocument replaceWordTemplate(XyWordTemplate template,Object... dataSets) throws Exception {
        //获得xdoc文档文件的提取器
        XWPFDocument doc = new XWPFDocument(POIXMLDocument.openPackage(template.getFilePath()));
        //将数据集转成模板中表达式与值的映射集
        Map<String,Object> dataSetMap = transDataSetToMap(dataSets);
        //第一步：替换模板中文本段落占位符
        replaceTextOfWord(doc,dataSetMap);
        //第二步：替换表格列中占位符及动态加载数据行
        Map<String,Object> surplusMap = replaceAndAppendTableofWord(doc,dataSetMap);
        //第三步：替换段落中引用的涉及表格求和的标记符
        replaceTextOfWord(doc,surplusMap);
        return doc;
    }

    /**
     * 解析word中的文本，并按照模板变量进行替换
     * @param doc
     * @return
     */
    protected void replaceTextOfWord(XWPFDocument doc,Map<String,Object> map) {
        try {
            //获得文档段落迭代器
            Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
            while (iterator.hasNext()){
                //获得当前段落对象
                XWPFParagraph paragraph = iterator.next();
                List<XWPFRun> runs = paragraph.getRuns();
                //匹配当前段落是否设置模板变量，如果设置执行if语句里面的内容
                for(int i = 0;i < runs.size();i++){
                    //获得当前段落文本内容
                    String text = runs.get(i).getText(runs.get(i).getTextPosition());
                    if(text != null){
                        for (Map.Entry<String, Object> entry : map.entrySet()) {
                            if (text.contains(entry.getKey())) {
                                if(!(entry.getValue() instanceof List)){
                                    text = text.replace(entry.getKey(), entry.getValue().toString());
                                }
                            }
                        }
                    }
                    runs.get(i).setText(text,0);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 替换表格内容以及动态生成表格的行
     * @param doc
     * @param map
     *     数据集映射集对象
     */
    protected Map<String,Object> replaceAndAppendTableofWord(XWPFDocument doc,Map<String,Object> map)
            throws IllegalAccessException {
        //使用LinkedHashMap保证元素有序
        Map<String,Object> resMap = new LinkedHashMap<>();
        //获得word模板中所有表格
        List<XWPFTable> tables = doc.getTables();
        if(tables.size() < 1)
            return null;
        for(XWPFTable table:tables){
            List<XWPFTableRow> rows = table.getRows();
            //得到模板数据行的位置
            int pos = getDataRowPos(rows);
            //获得第一个数据行，第一个数据行作为模板行，用来获取每一列的占位符
            XWPFTableRow row = rows.get(pos);
            //添加数据行,并返回新添加行的列表
            List<XWPFTableRow> newRows = addRowList(table,row,map,pos);
            //删除模板行
            if(newRows.size() > 0){
                table.removeRow(pos);
            }
            //替换剩余行的相关内容，数据行不支持添加函数，例如sum  upper
            resMap.putAll(replaceSurplusRows(table,map,pos,newRows));
        }
        return resMap;
    }

    /**
     * 替换剩余行的相关内容
     * 两个形参，start，end用来圈定新数据行的范围
     * @param table
     * @param map
     * @param  start
     *        数据行的开始位置
     * @param  newRows
     *        新添加的数据行列表
     * @return
     */
    private Map<String,Object> replaceSurplusRows(XWPFTable table,Map<String,Object> map,int start,List<XWPFTableRow> newRows){
        List<XWPFTableRow> rows = table.getRows();
        //剩余待处理的占位符，数据集中不存在，在表格中出现的占位符，程序只涉及UPPER，SUM两个函数
        Map<String,Object> surplusMap = new LinkedHashMap<>();
        //遍历当前表格的所有行
        for(int i = 0;i < rows.size();i++){
           List<XWPFTableCell>  cells= rows.get(i).getTableCells();
           for(XWPFTableCell cell:cells){
               String text = cell.getText();
               //第一步，替换表格中使用到数据集中的占位符，例如:${XY.USER.NAME}  ${XY.UPPER(${XY.USER.NAME})}
               replaceSurplusRowsForDataSet(cell,map,text);
               //第二步，替换表格中使用到函数的占位符如下所示
               //1、${XY.SUM(TABLE[1]:4)}
               //2、${XY.UPPER(${XY.SUM(TABLE[1]:4)})}
               surplusMap.putAll(replaceSurplusRowsForTable(table,cell,newRows));

           }
        }
        return surplusMap;
    }

    /**
     * 第二步，替换表格中使用到函数的占位符如下所示
     * @param table
     * @param cell
     * @param text
     */
    private Map<String,Object> replaceSurplusRowsForTable(XWPFTable table,XWPFTableCell cell,List<XWPFTableRow> newRows){
        String text = cell.getText();
        //尝试获取是否包含${XY.UPPER(${XY.SUM(TABLE[1]:4)})}格式的占位符
        Map<String,String> map = getRegexMapForUpperOrSum(upperAndSumRegex,text);
        //将表格中的UPPER函数  或者 SUM函数放入映射集，因为文本段落中涉及对表格中汇总数据的引用
        //将其放入映射集，作为文本段落替换的参数，重新再去更新一次文本段落中在表格里出现的标记符，此程序只涉及SUM UPPER两个函数
        //采用LinkedHashMap保证元素的有序性，因为占位标记符，UPPER  SUM函数的顺序不能乱序，否则替换会有误
        Map<String,Object> resMap = new LinkedHashMap<>();

        for(Map.Entry<String,String> entry : map.entrySet()){
            //${XY.UPPER(${XY.SUM(TABLE[1]:4)})}
            String key = entry.getValue();
            String val = entry.getKey();
            //如果当前占位符 包含SUM函数
            if(isContainsMark(sumRegx,val)){
                int cellIndex = Integer.valueOf(val.substring(val.indexOf(":") + 1,val.lastIndexOf(")")));
                //获得指定列的和
                String sum = sumCell(newRows,cellIndex - 1);
                val = XyRmbTransLowerOrUpper.transAmountToUpper(sum);
                //替换表格中的内容
                replaceCellText(cell,key,val);
                //将本次处理的内容放入映射集缓存
                resMap.put(key,val);
                resMap.put(key.substring(key.indexOf("(") + 1,key.lastIndexOf(")")),sum);
            }
        }

        //尝试获取是否包含${XY.SUM(TABLE[1]:4)}格式的占位符,因为UPPER已经处理过了，在这里不进行处理
        //if(!text.contains("XY.UPPER")){
            map = getRegexMapForUpperOrSum(sumRegx,text);
            for(Map.Entry<String,String> entry : map.entrySet()){
                //得到SUM函数括号里的内容
                String val = entry.getKey();
                String key = entry.getValue();
                int cellIndex = Integer.valueOf(val.substring(val.indexOf(":") + 1,val.indexOf(":") + 2));
                Object targetVal = "";
                //因为如果UPPER 与  SUM函数是嵌套使用的，那么里层的SUM函数已经计算完成，并放入映射集
                //如果出现已经计算完成，直接设置值即可
                if(resMap.get(entry.getValue()) != null){
                    targetVal = resMap.get(entry.getValue());
                }else{
                    //如果没有重新计算
                    targetVal = sumCell(newRows,cellIndex - 1);
                }
                replaceCellText(cell,key,targetVal.toString());
                resMap.put(key,targetVal);
            }
        //}
        return resMap;
    }

    /**
     * 设置指定列的内容
     * @param cell
     * @param key
     *        待要替换的占位符
     * @param val
     *        targetVal
     */
    private void replaceCellText(XWPFTableCell cell,String key,String val){
        List<XWPFParagraph> paragraphs = cell.getParagraphs();
        for(XWPFParagraph paragraph : paragraphs){
            List<XWPFRun> runs = paragraph.getRuns();
            for(int i = 0;i < runs.size();i++){
                String targetVal = runs.get(i).getText(runs.get(i).getTextPosition());
                targetVal = targetVal.replace(key.trim(),val);
                runs.get(i).setText(targetVal,0);
            }
        }
    }

    /**
     * 将指定范围内的行
     * @param rows
     * @param cellIndex
     * @return
     */
    private String sumCell(List<XWPFTableRow> rows,int cellIndex){
        BigDecimal sum = new BigDecimal("0");
        for(XWPFTableRow row:rows){
            String val = row.getCell(cellIndex).getText();
            if(val != null && !"".equals(val)){
                sum = sum.add(new BigDecimal(val));
            }
        }
        return sum.toString();
    }

    /**
     * 如果表格中有维护的占位符是直接取自数据集
     * @param cell
     * @param map
     * @param text
     */
    private void replaceSurplusRowsForDataSet(XWPFTableCell cell,Map<String,Object> map,String text){
        //先匹配数据集中的值
        for(Map.Entry<String,Object> entry:map.entrySet()){
            String key = entry.getKey();
            String value = entry.getValue().toString();
            //首先按照最外层数据集尝试进行替换
            if(text.contains(key)){
                //匹配是否包含${XY.UPPER(${XY.USER.NAME})}
                Map<String,String> regexMap = getRegexMapForUpperOrSum(regexForUpper,text);
                if(regexMap.get(key) != null){
                    key = regexMap.get(key);
                    value = XyRmbTransLowerOrUpper.transAmountToUpper(value);
                }
                //非数据行按照段落进行匹配
                //获取当前列所有的段落
                List<XWPFParagraph> paragraphs = cell.getParagraphs();
                //遍历段落
                for(XWPFParagraph paragraph:paragraphs){
                    List<XWPFRun> runs = paragraph.getRuns();
                    for(int j = 0;j < runs.size();j++){
                        String runText = runs.get(j).getText(runs.get(j).getTextPosition());
                        runText = runText.replace(key,value);
                        runs.get(j).setText(runText,0);
                    }
                }
            }
        }
    }

    /**
     * 获得数据行指定索引列的和
     * @param table
     * @param cellPos
     *        待求和的列的位置
     * @param start
     *        数据行的开始位置
     * @param end
     *        数据行的结束位置
     * @return
     */
    private String sumCellBetweenRows(XWPFTable table,int cellPos,int start,int end){
        BigDecimal sum = new BigDecimal("0");
        List<XWPFTableRow> rows = table.getRows();
        for(XWPFTableRow row:rows){
            String text = row.getCell(cellPos).getText();
            if((text != null) && (!"".equals(text))){
                sum = sum.add(new BigDecimal(text));
            }
        }
        return sum.toString();
    }

    /**
     * 获取数据行的位置,1次只取一个
     * @param rows
     * @return
     */
    private int getDataRowPos(List<XWPFTableRow> rows){
        int dataRowIndex = 0;
        for(XWPFTableRow row:rows){
            //如果是数据行，第一列内容即可判断，如果是数据行，并得到其索引
            String cellText = row.getCell(0).getText();
            dataRowIndex++;
            break;
        }
       return dataRowIndex;
    }

    /**
     * 添加数据行
     * @param table
     * @param row
     * @param map
     * @param dataRowIndex
     * @return
     *    添加的行数
     * @throws IllegalAccessException
     */
    private List<XWPFTableRow> addRowList(XWPFTable table,XWPFTableRow row,Map<String,Object> map,int dataRowIndex)
            throws IllegalAccessException {
        List<XWPFTableRow> newRows = new ArrayList<>();
        //从map中找到对应的数据集列表
        for(Map.Entry<String,Object> entry:map.entrySet()){
            String key = entry.getKey();
            //获得数据集名+属性名的组合  例如：MyDataSet.list
            String setStr = key.substring(key.indexOf(".")+1,key.length()-1);
            //找到此表格对应的数据集，可能一个数据集类中存在两个list列表
            //通过第一列判断是否是同一个数据集的同一个属性
            String cellText = row.getCell(0).getText();
            if(cellText.contains(setStr)){
                if(entry.getValue() instanceof List){
                    List dataList = (List) entry.getValue();
                    //nums = dataList.size();
                    if(dataList.size() > 0){
                        //得到list列表的泛型类名称
                        String className = dataList.get(0).getClass().getName();
                        //继续按照占位符格式构造key值，到这里变为：数据集名+列表属性名+列表中元素对象名例如
                        //例如：MyDataSet.list.User
                        //prefix将在beansToHashMap中加工成word模板中的表现形式例如:${XY[MyDataSet.list.User.userName]}
                        final String prefix = setStr + "." + className.
                                substring(className.lastIndexOf(".") + 1,className.length());
                        //接下来开始添加行，循环列表添加
                        int rowIndex = 0;
                        for(int i = 0;i < dataList.size();i++){
                            //获得映射集
                            rowIndex++;
                            //将列表中的元素转换成map映射例如：map{key:${XY[MyDataSet.list.User.userName]},value:"小野"}
                            //替换的时候直接按照k，v形式替换即可
                            Map<String,String> dataMap = beansToHashMap(dataList.get(i),prefix,rowIndex);
                            //循环插入行
                            XWPFTableRow newRow = table.insertNewTableRow(++dataRowIndex);
                            copyRowFromSourceRow(row,newRow,dataMap);
                            newRows.add(newRow);
                        }

                    }
                }
            }
        }
        return newRows;
    }

    /**
     * 复制表格属性
     * @param sourceRow
     *        模板行
     *        行号
     * @param targetRow
     *        新插入的行
     * @param dataMaps
     *        数据集映射集，key：需要替换的字符串，value：目标值
     */
    private void copyRowFromSourceRow(XWPFTableRow sourceRow,XWPFTableRow targetRow,Map<String,String> dataMaps) {
        //复制行属性
        targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
        List<XWPFTableCell> cellList = sourceRow.getTableCells();
        if(null==cellList) {
            return ;
        }
        //添加列、复制列以及列中段落属性
        XWPFTableCell targetCell = null;
        for(XWPFTableCell sourceCell:cellList) {
            targetCell = targetRow.addNewTableCell();
            //列属性
            targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
            //段落属性
            targetCell.getParagraphs().get(0).getCTP().setPPr(sourceCell.getParagraphs().get(0).getCTP().getPPr());
            //段落内容
            targetCell.getParagraphs().get(0).removeRun(0);
            XWPFRun run = targetCell.getParagraphs().get(0).createRun();
            //列内容
            for(Map.Entry<String,String> entry:dataMaps.entrySet()){
                if(entry.getKey().equals(sourceCell.getText())){
                    run.setText(sourceCell.getText().replace(entry.getKey(),entry.getValue()),0);
                }
            }

        }
    }

    /**
     * 判断word模板中是否包含指定的字符串
     * @param regex
     *      正则表达式
     * @param str
     *       待要检查的字符串
     * @return
     */
    private boolean isContainsMark(String regex,String str){
        boolean is = false;
        if(str == null || str.equals(""))
            return is;
        return Pattern.matches(regex,str);
    }

    /**
     * 判断一个占位符是否代表是数据行
     * @param cellText
     * @return
     */
    private boolean isDataRow(String cellText){
        String regex = "\\$\\{XY\\[(.*?)\\.(.*?)\\.(.*?)\\.(.*?)\\]\\}";
        return Pattern.matches(regex,cellText);
    }

    /**
     * 将数据集转换为HashMap
     * @param dataSets
     *        数据集集合
     * @return
     */
    public Map<String,Object> transDataSetToMap(Object... dataSets)
            throws Exception {
          //初始化字段映射集  key=${XY.数据集名称.字段名}，value=字段值
          Map<String,Object> fieldMap = new HashMap<>();
          //遍历数据集
          for(Object obj:dataSets){
              String className = obj.getClass().getName();
              //数据集名称：类名
              String dataSetName = className.substring(className.lastIndexOf(".") + 1,className.length());
              Field[] fields = obj.getClass().getDeclaredFields();
              for(Field field:fields){
                  field.setAccessible(true);
                  String k = null;
                  Object v = field.get(obj);
                  k = "${"+prefix+"."+dataSetName+"."+field.getName()+"}";
                  fieldMap.put(k,v);
              }
          }
        return fieldMap;
    }

    /**
     * 将普通的bean转为HashMap 在这里主要用来解析列表中的bean对象
     * @param bean
     * @param prefix  前缀
     * @return
     */
    public Map<String,String> beansToHashMap(Object bean,String prefix,int rowIndex) throws IllegalAccessException {
        Field[] fields = bean.getClass().getDeclaredFields();
        Map<String,String> beanMap = new HashMap<>();
        for(Field field:fields){
            field.setAccessible(true);
            String key = "${XY["+prefix+"."+field.getName()+"]}";
            beanMap.put(key,String.valueOf(field.get(bean)));
        }
        //插入索引列
        beanMap.put("${XY["+prefix+"."+"ROWINDEX]}",String.valueOf(rowIndex));
        return beanMap;
    }

    /**
     * 通过正则表达式匹配到的字符串放入映射集
      * @param regex
     * @param s
     *          结束位置
     * @return
     */
    private Map<String,String> getRegexMapForUpperOrSum(String regex,String s){
        Map<String,String> map = new HashMap<>();
        Pattern p = Pattern.compile(regex);
        Matcher matcher = p.matcher(s);
        while (matcher.find()){
            String regexStr = matcher.group();
            if(regexStr != null && !"".equals(regexStr)){
                map.put(regexStr.substring(regexStr.indexOf("(") + 1,regexStr.length() - 2),regexStr);
            }
        }
        return map;
    }

    public static void main(String[] args) throws Exception{
        //第一步：可以设计一个模板对象，将模板信息封装至此对象
        XyWordTemplate template = new XyWordTemplate();
        //template.setFilePath("/Users/xiaoqian/Desktop/files/mu.docx");
        template.setFilePath("/Users/xiaoqian/Desktop/files/demo-template.docx");
        //根据自己的方式生成数据集，数据集类中动态数据行用List标识，例如列入用户列表List<User>
        List<SupplyList> supplyLists = new ArrayList<>();
        SupplyList supplyList = new SupplyList();
        supplyList.setDelAdd("烟台市芝罘区");
        supplyList.setDelDate("2021/1/19");
        supplyList.setGoodsName("智慧动能电池60型");
        supplyList.setItemPrice("45.60");
        supplyList.setModel("60M");
        supplyList.setItemPrice("4500.61");
        supplyList.setNoRatioPrice("1621.56");
        supplyList.setNums("10");
        supplyList.setItemPrice("1564.97");
        supplyList.setProjUnit("临沂市光明电力有限责任公司林数分公司");
        supplyList.setNoRatioPrice("4671.78");
        supplyList.setProjName("aaaaaa");


        SupplyList supplyList1 = new SupplyList();
        supplyList1.setDelAdd("烟台市芝罘区2");
        supplyList1.setDelDate("2021/1/19");
        supplyList1.setGoodsName("智慧动能电池61型");
        supplyList1.setItemPrice("45.60");
        supplyList1.setModel("60M");
        supplyList1.setItemPrice("4500.61");
        supplyList1.setNoRatioPrice("1621.56");
        supplyList1.setNums("10");
        supplyList1.setProjUnit("临沂市光明电力有限责任公司林数分公司2");
        supplyList1.setNoRatioPrice("4671.78");
        supplyList1.setItemPrice("1671.78");
        supplyList1.setProjName("bbbbbb");
        supplyLists.add(supplyList);
        supplyLists.add(supplyList1);


        SupplyListDataSet dataSet = new SupplyListDataSet();
        dataSet.setList(supplyLists);
        dataSet.setOrderNo("X00000001");
        dataSet.setSaleName("上海桑瑞电子科技有限公司");
        dataSet.setSupplyNo("S0000002");

        //第三步：替换word模板中的占位符并返回文档对象
        XWPFDocument doc = XyWordTemplateUtil.newInstance().replaceWordTemplate(template,dataSet);
        //第三步：下载文件
        FileOutputStream outStream = null;
        outStream = new FileOutputStream("/Users/xiaoqian/Desktop/files/supply01.docx");
        doc.write(outStream);
        outStream.close();
        String s = "\\$\\{XY\\.SUM\\(TABLE\\[([1-9]\\d*)\\]\\:([1-9]\\d*)\\)\\}";
       // System.out.println(Pattern.matches(s,"${XY.SUM(TABLE[1]:10)}"));
    }
}
